package com.wendu.oa.common;

import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTDecodeException;
import com.auth0.jwt.exceptions.JWTVerificationException;
import com.auth0.jwt.exceptions.TokenExpiredException;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.wendu.oa.WdOaApp;
import com.wendu.oa.common.dao.SecurityDao;
import com.wendu.oa.exception.BusinessException;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import javax.annotation.Resource;
import java.util.Date;
import java.util.concurrent.TimeUnit;


public class TokenUtils {


    /**
     * 过期时间
     */
    private static final int EXPIRED_TIME = 99999999; //单位s

    private static final String CLAIM_USERID = "USERID";
    private static final String CLAIM_USERNAME = "USERNAME";

    private static String sign(CurrentUser currentUser,String securityKey){
        Algorithm algorithm = Algorithm.HMAC256(securityKey);
        String token = JWT.create()
                        .withExpiresAt(new Date(System.currentTimeMillis()+EXPIRED_TIME*1000))
                        .withClaim(CLAIM_USERID,currentUser.getUserId())
                        .withClaim(CLAIM_USERNAME, currentUser.getUserName())
                        .sign(algorithm);
        return token;
    }

    /**
     * 使用当前用户信息和当前用户密码生成令牌
     * @param currentUser
     * @param password
     * @return
     */
    public static String loginSign(CurrentUser currentUser,String password){
        String token = sign(currentUser, password);
        //将令牌同时以键和值存入redis
        stringRedisTemplate().opsForValue().set(token, token, EXPIRED_TIME*2, TimeUnit.SECONDS);
        return token;
    }

    /**
     * 依据客户端的令牌获得当前用户信息
     * @param clientToken
     * @return
     */
    public static CurrentUser getCurrentUser(String clientToken){

        if(!StringUtils.hasText(clientToken)){
            throw new BusinessException("令牌为空！请登录。");
        }

        //获得解码后的令牌
        DecodedJWT decodedJWT = null;
        try {
            decodedJWT = JWT.decode(clientToken);
        } catch (JWTDecodeException e) {
            throw new BusinessException("令牌错误！请登录。",e);
        }

        String userId = decodedJWT.getClaim(CLAIM_USERID).asString();
        String userName = decodedJWT.getClaim(CLAIM_USERNAME).asString();

        if(!StringUtils.hasText(userId) || !StringUtils.hasText(userName)){
            throw new BusinessException("令牌缺失用户信息！请登录。");
        }

        return new CurrentUser(userId,userName);

    }


    //清除缓存中的token
    public static void  removeToken(String clientToken){
        stringRedisTemplate().delete(clientToken);
    }

    //获取SecurityDao对象
    private static SecurityDao securityDao(){
        return WdOaApp.getApplicationContext().getBean(SecurityDao.class);
    }

    //获取StringRedisTemplate对象（spring提供的操作redis的对象，专门针对String类型的键值对）
    private static StringRedisTemplate stringRedisTemplate(){
        return WdOaApp.getApplicationContext().getBean(StringRedisTemplate.class);
    }

    //验证令牌
    public static void verify(String clientToken){

        //1. 验证客户端令牌是否为空
        //2. 验证客户端令牌中是否有当前用户信息
        CurrentUser currentUser = getCurrentUser(clientToken);



        //3.用密钥验证令牌是否合法
        //(1)访问数据库获得密钥
        String password = securityDao().findPasswordByUserId(currentUser.getUserId());
        if(!StringUtils.hasText(password)){
            throw new BusinessException("账号无对应密码！请登录。");
        }
        //(2)通过密钥生成验证器
        JWTVerifier verifier = JWT.require(Algorithm.HMAC256(password)).build();

        //（3）通过客户端令牌获取缓存令牌
        String cacheToken = stringRedisTemplate().opsForValue().get(clientToken);
        if(!StringUtils.hasText(cacheToken)){
            throw new BusinessException("无令牌或者令牌失效！请重新登录。");
        }

        //（4）使用验证器验证缓存令牌
        try {
            verifier.verify(cacheToken);
        } catch (TokenExpiredException e) {//缓存令牌本省过期
            //签名一个新令牌
            String newToken = sign(currentUser,password);

            //以客户端令牌为键，以新令牌为值，重新放入reids中，并重设过期时间
            stringRedisTemplate().opsForValue()
                    .set(clientToken, newToken, EXPIRED_TIME*2, TimeUnit.SECONDS);


        } catch (JWTVerificationException e) {//令牌非法
            throw new BusinessException("令牌非法！请登录。",e);
        } catch (Exception e){//令牌非法
            throw new BusinessException("令牌非法！请登录。",e);
        }


    }
}
